#!/usr/bin/env python
#
# Copyright 2006 Bret Taylor
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Usage: generateast.py < indie.ast

Converts the textual AST specification to C++ classes.
"""

__author__ = 'Bret Taylor <btaylor@gmail.com>'

import re
import sys

# Our tab width
_TAB_SIZE = 4

class Class:
  def __init__(self, name, superclass, abstract):
    self.name = name
    self.superclass = superclass
    self.abstract = abstract
    self.members = []


class Member:
  def __init__(self, type, name, repeated, optional):
    self.type = type
    self.name = name
    self.repeated = repeated
    self.optional = optional


def main(argv):
  classes = ParseAst(sys.stdin)
  PrintAstHeader(open("ast.h", "w"), classes)
  PrintAstBody(open("ast.cc", "w"), classes)
  PrintVisitorHeader(open("visitor.h", "w"), classes)
  PrintVisitorBody(open("visitor.cc", "w"), classes)
  PrintAstPrinterHeader(open("astprinter.h", "w"), classes)
  PrintAstPrinterBody(open("astprinter.cc", "w"), classes)


def PrintAstHeader(file, classes):
  file.write(_AST_H_TOP)
  file.write("\n\n")
  for cls in classes:
    PrintClassHeader(file, cls)
  file.write(_AST_H_BOTTOM)

def PrintAstBody(file, classes):
  file.write(_AST_CC_TOP)
  file.write("\n\n")
  for cls in classes:
    PrintClassBody(file, cls)
  file.write(_AST_CC_BOTTOM)


def PrintVisitorHeader(file, classes):
  file.write(_VISITOR_H_TOP)

  # Enter methods
  for cls in classes:
    if not cls.abstract:
      PrintLine(file, [ "virtual void Enter(const %s& ast);" %
                        ClassName(cls.name) ], 1)
  file.write("\n")

  # Leave methods
  for cls in classes:
    if not cls.abstract:
      PrintLine(file, [ "virtual void Leave(const %s& ast);" %
                        ClassName(cls.name) ], 1)

  file.write(_VISITOR_H_BOTTOM)


def PrintVisitorBody(file, classes):
  file.write(_VISITOR_CC_TOP)

  # Enter methods
  for cls in classes:
    if not cls.abstract:
      PrintLine(file, [ "void Visitor::Enter(const %s& ast) {" %
                        ClassName(cls.name) ], 0)
      PrintLine(file, [ "EnterDefault(ast);" ], 1)
      PrintLine(file, [ "}" ], 0)
      file.write("\n")

  # Leave methods
  for cls in classes:
    if not cls.abstract:
      PrintLine(file, [ "void Visitor::Leave(const %s& ast) {" %
                        ClassName(cls.name) ], 0)
      PrintLine(file, [ "LeaveDefault(ast);" ], 1)
      PrintLine(file, [ "}" ], 0)
      file.write("\n")

  file.write(_VISITOR_CC_BOTTOM)


def PrintAstPrinterHeader(file, classes):
  file.write(_ASTPRINTER_H_TOP)

  for cls in classes:
    if not cls.abstract:
      PrintLine(file, [ "virtual void Enter(const %s& ast);" %
                        ClassName(cls.name) ], 1)
  file.write("\n")

  file.write(_ASTPRINTER_H_BOTTOM)


def PrintAstPrinterBody(file, classes):
  file.write(_ASTPRINTER_CC_TOP)

  # Enter methods
  for cls in classes:
    if not cls.abstract:
      PrintLine(file, [ "void AstPrinter::Enter(const %s& ast) {" %
                        ClassName(cls.name) ], 0)
      PrintLine(file, [ "PrintNode(\"%s\");" % cls.name ], 1)
      PrintLine(file, [ "}" ], 0)
      file.write("\n")

  file.write(_ASTPRINTER_CC_BOTTOM)


def PrintClassHeader(file, cls):
  class_name = ClassName(cls.name)
  super_name = "Ast"
  if cls.superclass != None: super_name = ClassName(cls.superclass)

  # Class definition
  PrintLine(file, [
    "class %s" % class_name,
    " : public %s {" % super_name,
  ], 0)
  PrintLine(file, [ "  public:" ], 0)

  # Constructor and destructor
  if not cls.abstract:
    PrintConstructor(file, cls, class_name, 1, ";")
  PrintLine(file, [ "virtual ~%s();" % class_name ], 1)

  # Visitor support
  if not cls.abstract:
    file.write("\n")
    PrintLine(file, [ "virtual void Accept(Visitor& visitor) const;" ], 1);

  # Accessors
  for member in cls.members:
    file.write("\n")

    if member.optional:
      PrintLine(file, [ "inline bool has_%s() const {" %  member.name ], 1)
      PrintLine(file, [ "return %s_.get() != NULL;" %  member.name ], 2)
      PrintLine(file, [ "}" ], 1)
      file.write("\n")

    PrintLine(file, [ "inline %s %s() const {" %
                      (AccessorType(member), member.name) ], 1)
    if member.optional:
      PrintLine(file, [ "assert(has_%s());" % member.name ], 2)
    dereference = "*"
    if IsPodType(member.type): dereference = ""
    PrintLine(file, [ "return %s%s_;" % (dereference, member.name) ], 2)
    PrintLine(file, [ "}" ], 1)

  if cls.abstract:
    file.write("\n")
    PrintLine(file, [ "  protected:" ], 0)
    PrintConstructor(file, cls, class_name, 1, ";")

  # Member variables
  if len(cls.members) > 0:
    file.write("\n")
    PrintLine(file, [ "  private:" ], 0)
    for member in cls.members:
      PrintLine(file, [ "%s %s_;" % (MemberType(member), member.name) ], 1)

  # End class
  PrintLine(file, [ "};" ], 0)
  file.write("\n\n")


def PrintClassBody(file, cls):
  class_name = ClassName(cls.name)
  super_name = "Ast"
  if cls.superclass != None: super_name = ClassName(cls.superclass)

  # Constructor and initializers
  PrintConstructor(file, cls, "%s::%s" % (class_name, class_name), 0, " :")

  # Initializers
  line = [ "%s(location)" % super_name ]
  for member in cls.members:
    line.append("%s_(%s)" % (member.name, member.name))
  AddListConnectors(line, ", ", " {")
  PrintLine(file, line, 1, False)

  # Make sure non-optional members are non-NULL
  for member in cls.members:
    if not member.optional and not IsPodType(member.type):
      PrintLine(file, [ "assert(%s != NULL);" % member.name ], 1)

  PrintLine(file, [ "}" ], 0)

  # Destructor
  file.write("\n")
  PrintLine(file, [ "%s::~%s() {" % (class_name, class_name) ], 0)
  PrintLine(file, [ "}" ], 0)

  # Visitor Accept implementation
  if not cls.abstract:
    file.write("\n")
    PrintLine(file, [ "void %s::Accept(Visitor& visitor) const {" %
                      class_name ], 0);
    PrintLine(file, [ "visitor.Enter(*this);" ], 1)
    for member in cls.members:
      if IsAstType(member.type):
        if (member.repeated):
          lines = [ "for_each(%s().begin(), " % member.name,
                    "%s().end(), " % member.name,
                    "AcceptFunctor(visitor));" ]
          PrintLine(file, lines, 1)
        else:
          if member.optional:
            PrintLine(file, [ "if (has_%s()) {" % member.name ], 1)
            PrintLine(file, [ "%s().Accept(visitor);" % member.name ], 2)
            PrintLine(file, [ "}" ], 1)
          else:
            PrintLine(file, [ "%s().Accept(visitor);" % member.name ], 1)
    PrintLine(file, [ "visitor.Leave(*this);" ], 1)
    PrintLine(file, [ "}" ], 0)

  file.write("\n\n")


def PrintConstructor(file, cls, fn_name, num_tabs, end):
  line = [ "%s(FileLocation* location" % fn_name ]
  for member in cls.members:
    line.append("%s %s" % (ParameterType(member), member.name))
  AddListConnectors(line, ", ", ")" + end)
  PrintLine(file, line, num_tabs)


def PrintLine(file, tokens, num_tabs, paren_wrap = True):
  tab_size = num_tabs * _TAB_SIZE
  PrintChars(file, tab_size)
  file.write(tokens[0])

  # Wrap to the parenthesis if applicable
  wrap_tab_size = tab_size
  if paren_wrap:
    parenthesis_loc = tokens[0].find("(")
    if parenthesis_loc >= 0:
      wrap_tab_size += parenthesis_loc + 1

  # Print the remaining tokens, wrapping where applicable
  num_chars = tab_size + len(tokens[0])
  for token in tokens[1:]:
    if len(token) + num_chars > 80:
      file.write("\n")
      PrintChars(file, wrap_tab_size)
      num_chars = wrap_tab_size
    file.write(token)
    num_chars = num_chars + len(token)

  file.write("\n")


def PrintChars(file, num_chars):
  for i in range(num_chars):
    file.write(" ")


def AddListConnectors(list, middle, last):
  for i in range(0, len(list) - 1):
    list[i] += middle
  list[len(list) - 1] += last


def AccessorType(member):
  if IsPodType(member.type):
    return member.type
  if member.repeated:
    return "const vector<%s*>&" % ClassName(member.type)
  return "const %s&" % ClassName(member.type)


def MemberType(member):
  if IsPodType(member.type):
    return member.type
  if member.repeated:
    return "auto_collection_ptr<vector<%s*> >" % ClassName(member.type)
  return "auto_ptr<%s>" % ClassName(member.type)


def ParameterType(member):
  if IsPodType(member.type):
    return member.type
  if member.repeated:
    return "vector<%s*>*" % ClassName(member.type)
  return "%s*" % ClassName(member.type)


def IsAstType(name):
  return (name != "string" and name != "int" and name != "double" and
          name != "bool" and name != "UnaryOperator" and
          name != "BinaryOperator")


def IsPodType(name):
  return (name == "int" or name == "double" or name == "bool" or
          name == "UnaryOperator" or name == "BinaryOperator")


def ClassName(name):
  if IsAstType(name):
    return "Ast" + name
  else:
    return name


def ParseAst(file):
  classes = []
  class_re = re.compile("([^\\W]+)( *\\?)?(\\W*:\\W*([^\\W]+))?\\W*$")
  member_re = re.compile("\\W+([^\\W]+)\\W+([^\\W]+) *(\\*)? *(\\?)?\\W*$")
  line = file.readline()
  line_number = 1
  while line:
    m = class_re.match(line)
    if m != None:
      name = m.group(1)
      abstract = (m.group(2) != None)
      superclass = m.group(4)
      classes.append(Class(name, superclass, abstract))
    else:
      m = member_re.match(line)
      if m != None:
        if len(classes) == 0:
          Error(line, line_number, "Member must be declared within class")
        else:
          type = m.group(1)
          name = m.group(2)
          repeated = (m.group(3) != None)
          optional = (m.group(4) != None)
          last(classes).members.append(Member(type, name, repeated, optional))

    line = file.readline()
    line_number += 1

  return classes


def Error(line, line_number, message):
  raise Exception, "Error line %d: %s" % (line_number, message)


def last(list):
  return list[len(list) - 1]


_COPYRIGHTS = """// Copyright 2006 Bret Taylor
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
"""

_AST_H_TOP = _COPYRIGHTS + """
#ifndef INDIE_AST_H__
#define INDIE_AST_H__

#include <assert.h>
#include <memory>
#include <string>
#include <vector>

#include "filelocation.h"
#include "memory.h"

using std::auto_ptr;
using std::string;
using std::vector;

namespace indie {

// A Visitor recursively traverses an AST with the Ast::Accept method. We
// pre-declare it here to avoid circular dependencies.
class Visitor;

// The binary operators supported by Indie.
typedef enum {
    Addition,
    Subtraction,
    Multiplication,
    Division,
    And,
    Or,
    Equal,
    NotEqual,
    LessThan,
    GreaterThan,
    LessEqual,
    GreaterEqual,
    Mod
} BinaryOperator;

// The unary operators supported by Indie.
typedef enum {
    Not,
    Negative,
    PreDecrement,
    PreIncrement,
    PostDecrement,
    PostIncrement
} UnaryOperator;

// Our base AST class. An instance of a subclass of this class represents
// a single node in our abstract syntax tree.
class Ast {
  public:
    virtual ~Ast();

    virtual void Accept(Visitor& visitor) const = 0;

    inline bool has_location() const {
	return (location_.get() != NULL);
    }

    inline const FileLocation& location() const {
	assert(has_location());
	return *location_;
    }

  protected:
    Ast(FileLocation* location);

  private:
    auto_ptr<FileLocation> location_;
};
"""

_AST_H_BOTTOM = """// Utility class for parsing class members
class ClassMembers {
  public:
    vector<AstMethod*>* methods;
    vector<AstVariable*>* variables;
};

} // namespace indie

#endif // INDIE_AST_H__
"""

_AST_CC_TOP = _COPYRIGHTS + """
#include <algorithm>

#include "ast.h"
#include "visitor.h"

using std::for_each;

namespace indie {

// Utility class that lets us Accept all items in a collection with for_each.
class AcceptFunctor {
  public:
    AcceptFunctor(Visitor& visitor) : visitor_(visitor) {
    }

    template<class Type>
    void operator()(Type *ast) const {
        ast->Accept(visitor_);
    }

  private:
    Visitor& visitor_;
};


Ast::Ast(FileLocation* location) :
    location_(location) {
}

Ast::~Ast() {
}
"""

_AST_CC_BOTTOM = """} // namespace indie
"""

_VISITOR_H_TOP = _COPYRIGHTS + """
#ifndef INDIE_VISITOR_H__
#define INDIE_VISITOR_H__

#include "ast.h"

namespace indie {

class Visitor {
  public:
    virtual ~Visitor();

"""

_VISITOR_H_BOTTOM = """
  protected:
    Visitor();

    virtual void EnterDefault(const Ast& ast);
    virtual void LeaveDefault(const Ast& ast);
};

} // namespace indie

#endif // INDIE_VISITOR_H__
"""

_VISITOR_CC_TOP = _COPYRIGHTS + """
#include "visitor.h"

namespace indie {

Visitor::Visitor() {
}

Visitor::~Visitor() {
}

"""

_VISITOR_CC_BOTTOM = """void Visitor::EnterDefault(const Ast& ast) {
}

void Visitor::LeaveDefault(const Ast& ast) {
}

} // namespace indie
"""

_ASTPRINTER_H_TOP = _COPYRIGHTS + """
#ifndef INDIE_ASTPRINTER_H__
#define INDIE_ASTPRINTER_H__

#include <iostream>

#include "visitor.h"

using std::ostream;

namespace indie {

class AstPrinter : private Visitor {
  public:
    AstPrinter();
    virtual ~AstPrinter();

    void Print(const Ast& ast, ostream& stream);

  private:
"""

_ASTPRINTER_H_BOTTOM = """    virtual void LeaveDefault(const Ast& ast);

    void PrintNode(const char* node_type);

    ostream* stream_;
    unsigned int num_tabs_;
};

} // namespace indie

#endif // INDIE_ASTPRINTER_H__
"""

_ASTPRINTER_CC_TOP = _COPYRIGHTS + """
#include "astprinter.h"

namespace indie {

AstPrinter::AstPrinter() : Visitor(), stream_(NULL), num_tabs_(0) {
}

AstPrinter::~AstPrinter() {
}

void AstPrinter::Print(const Ast& ast, ostream& stream) {
    stream_ = &stream;
    num_tabs_ = 0;
    ast.Accept(*this);
    stream_ = NULL;
    num_tabs_ = 0;
}

"""

_ASTPRINTER_CC_BOTTOM = """void AstPrinter::LeaveDefault(const Ast& ast) {
    num_tabs_--;
}

void AstPrinter::PrintNode(const char* node_type) {
    for (unsigned int i = 0; i < num_tabs_; i++) {
	*stream_ << "  ";
    }
    *stream_ << node_type << std::endl;
    num_tabs_++;
}

} // namespace indie
"""


if __name__ == '__main__':
  main(sys.argv)
